// SKNotes - a note taking and gathering tool
// Copyright (C) 2009  Stefan Kueng
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

using System;
using System.Diagnostics;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;

namespace SKNotes.CustomControls
{
    /// <summary>
    /// Custom control that can switch between two modes: 
    /// editing and normal. 
    /// 
    /// This control is designed to be used in with a GridView View.
    /// </summary>
    public class EditBox : Control
    {

        #region Static Constructor

        /// <summary>
        /// Static constructor
        /// </summary>
        static EditBox( )
        {
            DefaultStyleKeyProperty.OverrideMetadata( typeof( EditBox ),
                new FrameworkPropertyMetadata( typeof( EditBox ) ) );
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Called when the tree for the EditBox has been generated.
        /// </summary>
        public override void OnApplyTemplate( )
        {
            base.OnApplyTemplate( );
            TextBlock textBlock = GetTemplateChild( "PART_TextBlockPart" )
                   as TextBlock;
            Debug.Assert( textBlock != null, "No TextBlock!" );

            _textBox = new TextBox( );
            _adorner = new EditBoxAdorner( textBlock, _textBox );
            AdornerLayer layer = AdornerLayer.GetAdornerLayer( textBlock );
            layer.Add( _adorner );

            _textBox.KeyDown += new KeyEventHandler( OnTextBoxKeyDown );
            _textBox.LostKeyboardFocus +=
              new KeyboardFocusChangedEventHandler( OnTextBoxLostKeyboardFocus );

            // Capture the resize event to  handle ListView resize cases.
            HookItemsControlEvents( );

            _listViewItem = GetDependencyObjectFromVisualTree( this,
                typeof( ListViewItem ) ) as ListViewItem;
            _treeViewItem = GetDependencyObjectFromVisualTree( this,
                typeof( TreeViewItem ) ) as TreeViewItem;

            Debug.Assert( _listViewItem != null || _treeViewItem != null, "No ListViewItem or TreeViewItem found" );
        }

        public void ValueChanged(object newValue)//object obj
        {
            UpdateViewItem( newValue );
        }

        #endregion

        #region Protected Methods

        protected void UpdateViewItem(object newValue)
        {
            object emp = null;
            if ( _listViewItem != null )
                emp = _listViewItem.Content as object;
            else if ( _treeViewItem != null )
                emp = _treeViewItem.DataContext as object;
            if ( emp != null )
            {
                foreach ( PropertyInfo prp in emp.GetType( ).GetProperties( ) )
                {
                    if ( prp.Name.ToLower( ) == this.DisplayMemberBindingName.ToLower( ) )
                        prp.SetValue( emp, newValue, null );
                }
            }
        }

        /// <summary>
        /// If the ListViewItem that contains the EditBox is selected, 
        /// when the mouse pointer moves over the EditBox, the corresponding
        /// MouseEnter event is the first of two events (MouseUp is the second)
        /// that allow the EditBox to change to editing mode.
        /// </summary>
        /// <param name="e"></param>
        protected override void OnMouseEnter(MouseEventArgs e)
        {
            base.OnMouseEnter( e );
            if ( !IsEditing && IsParentSelected )
            {
                _canBeEdit = true;
            }
        }

        /// <summary>
        /// If the MouseLeave event occurs for an EditBox control that
        /// is in normal mode, the mode cannot be changed to editing mode
        /// until a MouseEnter event followed by a MouseUp event occurs.
        /// </summary>
        /// <param name="e"></param>
        protected override void OnMouseLeave(MouseEventArgs e)
        {
            base.OnMouseLeave( e );
            _isMouseWithinScope = false;
            _canBeEdit = false;
        }

        /// <summary>
        /// An EditBox switches to editing mode when the MouseUp event occurs
        /// for that EditBox and the following conditions are satisfied:
        /// 1. A MouseEnter event for the EditBox occurred before the 
        /// MouseUp event.
        /// 2. The mouse did not leave the EditBox between the
        /// MouseEnter and MouseUp events.
        /// 3. The ListViewItem that contains the EditBox was selected
        /// when the MouseEnter event occurred.
        /// </summary>
        /// <param name="e"></param>
        protected override void OnMouseUp(MouseButtonEventArgs e)
        {
            base.OnMouseUp( e );

            if ( e.ChangedButton == MouseButton.Right ||
                e.ChangedButton == MouseButton.Middle )
                return;

            // only list view items are put in editing mode when clicked
            // on and they're selected. Tree view items are not: for tree view
            // items, use another method to start editing
            if ( ( !IsEditing ) && ( _listViewItem != null ) )
            {
                if ( !e.Handled && ( _canBeEdit || _isMouseWithinScope ) )
                {
                    IsEditing = true;
                }

                // If the first MouseUp event selects the parent ListViewItem,
                // then the second MouseUp event puts the EditBox in editing 
                // mode
                if ( IsParentSelected )
                    _isMouseWithinScope = true;
            }
        }

        #endregion

        #region Public Properties

        #region Value

        /// <summary>
        /// ValueProperty DependencyProperty.
        /// </summary>
        public static readonly DependencyProperty ValueProperty =
                DependencyProperty.Register(
                        "Value",
                        typeof( object ),
                        typeof( EditBox ),
                        new FrameworkPropertyMetadata( null ) );

        /// <summary>
        /// Gets or sets the value of the EditBox
        /// </summary>
        public object Value
        {
            get { return GetValue( ValueProperty ); }
            set { SetValue( ValueProperty, value ); }
        }

        #endregion

        #region DisplayMemberBindingName

        /// <summary>
        /// ValueProperty DependencyProperty.
        /// </summary>
        public static readonly DependencyProperty DisplayMemberBindingNameProperty =
                DependencyProperty.Register(
                        "DisplayMemberBindingName",
                        typeof( string ),
                        typeof( EditBox ),
                        new FrameworkPropertyMetadata( null ) );

        /// <summary>
        /// The value of the EditBox
        /// </summary>
        public string DisplayMemberBindingName
        {
            get { return GetValue( DisplayMemberBindingNameProperty ).ToString( ); }
            set
            {
                SetValue( DisplayMemberBindingNameProperty, value );
            }
        }

        #endregion

        #region IsEditing

        /// <summary>
        /// IsEditingProperty DependencyProperty
        /// </summary>
        public static DependencyProperty IsEditingProperty =
                DependencyProperty.Register(
                        "IsEditing",
                        typeof( bool ),
                        typeof( EditBox ),
                        new FrameworkPropertyMetadata( false ) );

        /// <summary>
        /// Returns true if the EditBox control is in editing mode.
        /// </summary>
        public bool IsEditing
        {
            get { return (bool)GetValue( IsEditingProperty ); }
            set
            {
                if (IsEditing == value)
                    return;
                if ( value )
                    _editString = (string)Value;
                SetValue( IsEditingProperty, value );
                _adorner.UpdateVisibilty( value );
                //This will update the edited ItemsSource of the ListView
                if ( _editString != _textBox.Text )
                {
                    _editString = _textBox.Text;
                    ValueChanged( _editString );
                }
            }
        }

        #endregion

        #region IsParentSelected

        /// <summary>
        /// Gets whether the ListViewItem that contains the 
        /// EditBox is selected.
        /// </summary>
        private bool IsParentSelected
        {
            get
            {
                if ( ( _listViewItem == null ) && ( _treeViewItem == null ) )
                    return false;
                else if ( _listViewItem != null )
                    return _listViewItem.IsSelected;
                else
                    return _treeViewItem.IsSelected;
            }
        }

        #endregion

        #endregion

        #region Private Methods

        /// <summary>
        /// When an EditBox is in editing mode, pressing the ENTER or F2
        /// keys switches the EditBox to normal mode.
        /// </summary>
        private void OnTextBoxKeyDown(object sender, KeyEventArgs e)
        {
            if ( IsEditing && ( e.Key == Key.Enter || e.Key == Key.F2 ) )
            {
                IsEditing = false;
                _canBeEdit = false;
            }
        }

        /// <summary>
        /// If an EditBox loses focus while it is in editing mode, 
        /// the EditBox mode switches to normal mode.
        /// </summary>
        private void OnTextBoxLostKeyboardFocus(object sender,
                                             KeyboardFocusChangedEventArgs e)
        {
            IsEditing = false;
        }

        /// <summary>
        /// Sets IsEditing to false when the ListViewItem that contains an
        /// EditBox changes its size
        /// </summary>
        private void OnCouldSwitchToNormalMode(object sender,
                                               RoutedEventArgs e)
        {
            IsEditing = false;
        }

        /// <summary>
        /// Walk the visual tree to find the ItemsControl and 
        /// hook its some events on it.
        /// </summary>
        private void HookItemsControlEvents( )
        {
            _itemsControl = GetDependencyObjectFromVisualTree( this,
                                typeof( ItemsControl ) ) as ItemsControl;
            if ( _itemsControl != null )
            {
                // Handle the ScrollChange/MouseWheel 
                // events to determine whether to switch to Normal mode
                _itemsControl.SizeChanged +=
                    new SizeChangedEventHandler( OnCouldSwitchToNormalMode );
                _itemsControl.AddHandler( ScrollViewer.ScrollChangedEvent,
                    new RoutedEventHandler( OnScrollViewerChanged ) );
                _itemsControl.AddHandler( ScrollViewer.MouseWheelEvent,
                    new RoutedEventHandler( OnCouldSwitchToNormalMode ), true );
            }
        }

        /// <summary>
        /// If an EditBox is in editing mode and the content of a ListView is
        /// scrolled, then the EditBox switches to normal mode.
        /// </summary>
        private void OnScrollViewerChanged(object sender, RoutedEventArgs args)
        {
            if ( IsEditing && Mouse.PrimaryDevice.LeftButton ==
                                MouseButtonState.Pressed )
            {
                IsEditing = false;
            }
        }

        /// <summary>
        /// Walk visual tree to find the first DependencyObject 
        /// of the specific type.
        /// </summary>
        private static DependencyObject
            GetDependencyObjectFromVisualTree(DependencyObject startObject,
                                              Type type)
        {
            // Walk the visual tree to get the parent(ItemsControl) 
            // of this control
            DependencyObject parent = startObject;
            while ( parent != null )
            {
                if ( type.IsInstanceOfType( parent ) )
                    break;
                else
                    parent = VisualTreeHelper.GetParent( parent );
            }

            return parent;
        }

        #endregion

        #region private variable

        private EditBoxAdorner _adorner;
        // A TextBox in the visual tree
        private TextBox _textBox;
        // Specifies whether an EditBox can switch to editing mode. 
        // Set to true if the ListViewItem that contains the EditBox is 
        // selected, when the mouse pointer moves over the EditBox
        private bool _canBeEdit;
        // Specifies whether an EditBox can switch to editing mode.
        // Set to true when the ListViewItem that contains the EditBox is 
        // selected when the mouse pointer moves over the EditBox.
        private bool _isMouseWithinScope;
        // The ListView control that contains the EditBox
        private ItemsControl _itemsControl;
        // The ListViewItem control that contains the EditBox
        private ListViewItem _listViewItem;
        // The TreeViewItem control that contains the EditBox
        private TreeViewItem _treeViewItem;
        // temporary string for editing
        private string _editString;

        #endregion
    }

}
